package individual.ircon.bilibili.network;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import individual.ircon.bilibili.bean.DanmuDataFrame;
import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;


/**
 * 用于处理弹幕服务器交互过程中的编码和解码问题。
 */
public class DanmuCoder {

    /**
     * 对帧进行编码，但是不对帧的内容进行检查和计算。只保证空间是数据+帧长。
     * @param total_len 帧的总体长度。但是和返回值byte数组的长度无关。返回的byte数组长度是 16 + data.length.
     * @param head_len 头的长度，固定为16.
     * @param version 协议版本。
     * @param group 内容类型。
     * @param other 其他。
     * @param data 数据
     * @return 帧的数据
     */
    public static byte[] frameEncoder(
            int total_len, short head_len, short version,
            int group, int other, byte[] data
    ){
        int len = Integer.BYTES*3 + Short.BYTES * 2 + (data == null ? 0 : data.length);
        ByteBuffer bb = ByteBuffer.allocate(len);
        bb.putInt(total_len);
        bb.putShort(head_len);
        bb.putShort(version);
        bb.putInt(group);
        bb.putInt(other);

        if(data != null)
            bb.put(data);
        return bb.array();
    }

    /**
     * 对帧进行解码。注意这里不会对帧的数据进行任何检查。
     * @param array 帧数据
     * @return 帧的数据对象
     */
    public static DanmuDataFrame frameDecoder(byte[] array) {
        if(array.length < 16) return null;
        ByteBuffer buffer = ByteBuffer.wrap(array);
        DanmuDataFrame frame = new DanmuDataFrame();

        frame.PackageLength = buffer.getInt();
        frame.HeadLength = buffer.getShort();
        frame.Version = buffer.getShort();
        frame.Category = buffer.getInt();
        frame.Other = buffer.getInt();
        if(array.length - 16 > 0) {
            byte[] data = new byte[buffer.remaining()];
            System.arraycopy(array, 16, data,0, data.length);
            frame.Data = data;
        }

        return frame;
    }
    /**
     * 把多个Json分割开。
     * @param jsons json串
     * @return 分开的json
     */
    public static String[] jsonSeparate(String jsons) {

        int state = 0;
        int left = 0;
        StringBuilder sb = new StringBuilder();
        ArrayList<String> list = new ArrayList<>();

        for(int i = 0; i < jsons.length(); ++i) {
            char v = jsons.charAt(i);

            if(v == '{'){
                if(left == 0) {
                    state = 1;
                }
                ++left;
                sb.append(v);
            }else if (v == '}') {
                if(left > 0){
                    --left;
                    sb.append(v);

                    if(left == 0) {
                        state = 0;
                        list.add(sb.toString());
                        sb.delete(0, sb.length());
                    }
                }
            }else {
                if(state == 1) {
                    sb.append(v);
                }
            }
        }

        String[] rest = new String[list.size()];
        for(int i = 0; i < rest.length; ++i) {
            rest[i] = list.get(i);
        }
        return rest;
    }

    /**
     * 根据单个弹幕message的json获取弹幕消息。如果返回为null，则意味着传入的参数有问题。
     * @param json json文本
     * @return 弹幕消息的对象
     */
    public static DanmuMessage getUserDanmu(String json){
        JSONObject jo = JSONObject.parseObject(json);
        DanmuMessage res = null;

        var v = jo.get("cmd");
        if(v != null && v.equals("DANMU_MSG")){
            var b = (JSONArray) jo.get("info");
            var c = (JSONArray) b.get(2);
            res= new DanmuMessage();

            res.UserID = Long.parseLong(c.get(0).toString());
            res.UserName = c.get(1).toString();
            res.Danmu = b.get(1).toString();

        }

        return res;
    }

    /**
     * 弹幕消息。
     */
    public static class DanmuMessage {
        public long UserID;
        public String UserName;
        public String Danmu;

        @Override
        public String toString() {
            return "DanmuMessage{" +
                    "UserID=" + UserID +
                    ", UserName='" + UserName + '\'' +
                    ", Danmu='" + Danmu + '\'' +
                    '}';
        }
    }

    /**
     * 把一个数据帧转化成一组弹幕对象。因为处理过程中有不是弹幕的服务器消息，这些消息会被处理成null存储下来。
     * @param array 数据帧
     * @return 弹幕对象的数组，如果为null，说明帧的数据帧的编码有问题。
     * @throws IOException IO异常，解码的时候。
     */
    public static DanmuMessage[] processFrame(byte[] array) throws IOException {
        var frame = frameDecoder(array);
        if (frame == null) return null; // 解码异常。

        while(frame.Version == (short) 3) { // 循环解码
            var buf = brotliDecode(frame.Data);
            frame = frameDecoder(buf);

            if(frame == null) return null;
        }

        // 转化成json并进行分割
        String js = new String(frame.Data, StandardCharsets.UTF_8);
        String[] jss = jsonSeparate(js);

        // 把json转化成弹幕对象。
        DanmuMessage[] message = new DanmuMessage[jss.length];

        for(int i = 0 ; i < message.length; ++i) {
            message[i] = getUserDanmu(jss[i]);
        }

        return message;
    }

    /**
     * 阶码Brotli数据.
     * @param data 数据数组
     * @return 解码后的数组（堆里面新建了一个）
     * @throws IOException exception occurs.
     */
    public static byte[] brotliDecode(byte[] data) throws IOException {
        if(data == null || data.length == 0)
            throw new IOException("The lenght of the byte array is worry.");
        var buff = new BrotliCompressorInputStream(new ByteArrayInputStream(data));
        return buff.readAllBytes();
    }

}
